package org.helgoboss.scala_additions

object Path {
    /**
     * Automatically applied by the compiler in case a Path is expected as argument but a String is given because it's 
     * in the companion object. No import of this implicit method necessary! Is that called a view?
     */
    implicit def string2Path(pathExpression: String) = Path(pathExpression)
    
    /**
     * Creates a path from the given path expression. The path expression is a slash-separated string.
     * The same normalizations are applied as in <code>apply(components)</code>.
     */
    def apply(pathExpression: String): Path = apply(splitIntoComponents(pathExpression))
    
    /**
     * Creates a path from the given path components. ".." is resolved. Empty components and components
     * only containing a dot are eliminated. Be aware that the concept of absolute and relative path is not supported.
     */
    def apply(components: Iterable[String]): Path = new Path(normalizeComponents(components))
    
    
    private def splitIntoComponents(pathExpression: String) = {
        pathExpression.split("/")
    }
    
    private def normalizeComponents(pathComponents: Iterable[String]) = {
        val normalizedComponents = new collection.mutable.Stack[String]
        pathComponents.foreach { c =>
            if (!c.isEmpty && c != ".") {
                if (c == "..") {
                    if (!normalizedComponents.isEmpty) normalizedComponents.pop
                } else {
                    normalizedComponents push c
                }
            }
        }
        normalizedComponents.toList.reverse
    }
}

/**
 * Represents a path, which basically consists of a sequence of strings plus some behavior typically
 * associated with paths, such as path pattern matching, parsing and creation of path 
 * representations using slash as separator and support for the special meaning of "." and "..".
 * 
 * For keeping things simple, there's no information about whether the path is absolute or relative.
 * The concept of absolute or relative can be added by the client if desired.
 */
class Path private (val components: List[String]) {
    /**
     * Returns the slash expression of the path.
     */
    override def toString = components.mkString("/")
    
    def matches(pathPattern: PathPattern) = pathPattern.matchAgainst(this) == PathPattern.Matches
    
    override def hashCode = components.hashCode
    
    def /(rightPath: Path) = new Path(components ++ rightPath.components)
    
    override def equals(other: Any) = other match {
        case otherPath: Path =>
           this.components == otherPath.components
            
        case _ => false
    }
}
